package com.atguigu.tingshu.search.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.pinyin.PinyinUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsAggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.aggregations.TopHitsAggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.*;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumInfoIndexRepository;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoIndexRepository albumInfoIndexRepository;


    @Autowired
    private AlbumFeignClient albumFeignClient;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private Executor threadPoolTaskExecutor;

    /**
     * 将指定专辑ID构建索引库文档对象，将文档保存到索引库
     *
     * @param albumId
     */
    @Override
    public void upperAlbum(Long albumId) {
        //1.封装索引库文档对象
        //1.1 创建空索引库对象
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
        //1.2 远程调用"专辑服务" 获取专辑信息（包含专辑标签列表）封装专辑基本信息 不依赖其他异步任务，返回结果给其他任务
        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
            Assert.notNull(albumInfo, "专辑{}不存在", albumId);
            //1.2.1 将基本信息采用拷贝属性方式
            BeanUtil.copyProperties(albumInfo, albumInfoIndex);
            //1.2.2 专辑中包含标签列表单独处理
            List<AlbumAttributeValue> albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList();
            if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
                //1.2.3 将集合泛型从AlbumAttributeValue转为AttributeValueIndex
                List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList
                        .stream()
                        .map(albumAttributeValue ->
                                BeanUtil.copyProperties(albumAttributeValue, AttributeValueIndex.class)
                        ).collect(Collectors.toList());
                //1.2.4 专辑索引库文档对象关联标签列表
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
            }
            return albumInfo;
        }, threadPoolTaskExecutor);


        //1.3 远程调用"专辑服务" 获取专辑三级分类ID 封装三级分类ID
        CompletableFuture<Void> cagetoryViewCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            BaseCategoryView categoryView = albumFeignClient.getCategoryView(albumInfo.getCategory3Id()).getData();
            Assert.notNull(categoryView, "分类{}不存在", albumInfo.getCategory3Id());
            albumInfoIndex.setCategory2Id(categoryView.getCategory2Id());
            albumInfoIndex.setCategory1Id(categoryView.getCategory1Id());
        }, threadPoolTaskExecutor);

        //1.4 TODO 远程调用"专辑服务" 获取专辑统计信息 封装统计信息
        //1.4 随机产生四项统计数值 播放量，订阅量，购买量，评论量
        int num1 = RandomUtil.randomInt(2000, 5000);
        int num2 = RandomUtil.randomInt(100, 1000);
        int num3 = RandomUtil.randomInt(1000, 3000);
        int num4 = RandomUtil.randomInt(500, 1000);
        albumInfoIndex.setPlayStatNum(num1);
        albumInfoIndex.setSubscribeStatNum(num2);
        albumInfoIndex.setBuyStatNum(num3);
        albumInfoIndex.setCommentStatNum(num4);
        //1.5 利用现有统计数值计算热度分值 规则：用户行为*系数 累加
        BigDecimal score1 = new BigDecimal("0.1").multiply(BigDecimal.valueOf(num1));
        BigDecimal score2 = new BigDecimal("0.2").multiply(BigDecimal.valueOf(num2));
        BigDecimal score3 = new BigDecimal("0.3").multiply(BigDecimal.valueOf(num3));
        BigDecimal score4 = new BigDecimal("0.4").multiply(BigDecimal.valueOf(num4));
        BigDecimal hotScore = score1.add(score2).add(score3).add(score4);
        albumInfoIndex.setHotScore(hotScore.doubleValue());

        //1.5 远程调用"用户服务" 获取专辑主播信息 封装主播名称
        CompletableFuture<Void> userInfoCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(albumInfo.getUserId()).getData();
            Assert.notNull(userInfoVo, "主播{}不存在", albumInfo.getUserId());
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
        }, threadPoolTaskExecutor);


        //1.6组合异步任务，要求所有任务必须执行结束
        CompletableFuture.allOf(
                albumInfoCompletableFuture,
                cagetoryViewCompletableFuture,
                userInfoCompletableFuture
        ).join();

        //2.保存专辑索引库文档
        albumInfoIndexRepository.save(albumInfoIndex);

        //3.将专辑标题存入提词索引库
        this.saveSuggestIndex(albumInfoIndex);

        //4.将专辑专辑ID存入布隆过滤器
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
        bloomFilter.add(albumInfoIndex.getId());
    }

    @Autowired
    private RedissonClient redissonClient;


    /**
     * 将指定专辑从索引库删除
     *
     * @param albumId
     */
    @Override
    public void lowerAlbum(Long albumId) {
        albumInfoIndexRepository.deleteById(albumId);
        suggestIndexRepository.deleteById(albumId.toString());
    }


    @Autowired
    private ElasticsearchClient elasticsearchClient;

    /**
     * 站内分页条件检索
     *
     * @param albumIndexQuery
     * @return
     */
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        try {
            //1.构建检索请求对象
            SearchRequest searchRequest = this.buildDSL(albumIndexQuery);
            System.out.println("本次检索构建DSL：");
            System.err.println(searchRequest);

            //2.调用ES提供检索RestfulAPI执行检索
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);

            //3.解析ES检索结果获取命中业务数据
            return this.parseSearchResult(searchResponse, albumIndexQuery);
        } catch (Exception e) {
            log.error("[检索服务]站内搜索异常：{}", e);
            throw new RuntimeException(e);
        }
    }

    //业务索引库名称
    private static final String INDEX_NAME = "albuminfo";

    /**
     * 根据检索条件封装检索请求对象
     *
     * @param albumIndexQuery
     * @return
     */
    @Override
    public SearchRequest buildDSL(AlbumIndexQuery albumIndexQuery) {
        //1.创建检索请求对象构建器对象：封装检索索引库名称
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(INDEX_NAME);
        //2.设置查询条件，请求体参数"query" 三大条件：1、关键字  2、分类 3、标签 采用bool must三大条件同时满足
        //2.1 创建封装三大条件bool查询对象
        BoolQuery.Builder allConditionBoolBuilder = new BoolQuery.Builder();
        //2.2 处理关键字查询条件 采用must,嵌套bool封装 三个子条件 采用should
        String keyword = albumIndexQuery.getKeyword();
        if (StringUtils.isNotBlank(keyword)) {
            //2.2.1 设置子条件1：匹配查询专辑标题  子条件2：匹配查询专辑简介 子条件3：精确查询主播名称
            allConditionBoolBuilder.must(m -> m.bool(
                    b -> b.should(s -> s.match(m1 -> m1.field("albumTitle").query(keyword)))
                            .should(s -> s.match(m1 -> m1.field("albumIntro").query(keyword)))
                            .should(s -> s.term(m1 -> m1.field("announcerName").value(keyword)))
            ));
        }
        //2.3 处理分类查询条件 1,2,3级分类 采用filer进行过滤分类
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id != null) {
            allConditionBoolBuilder.filter(f -> f.term(t -> t.field("category1Id").value(category1Id)));
        }
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id != null) {
            allConditionBoolBuilder.filter(f -> f.term(t -> t.field("category2Id").value(category2Id)));
        }
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id != null) {
            allConditionBoolBuilder.filter(f -> f.term(t -> t.field("category3Id").value(category3Id)));
        }

        //2.4 处理标签查询条件
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (CollectionUtil.isNotEmpty(attributeList)) {
            //2.5 遍历标签条件集合 每遍历一次设置Nested查询到最外层bool条件对象filter中
            for (String s : attributeList) {
                //2.5.1 采用":"进行分割 属性/标签id:标签值/属性值id
                String[] split = s.split(":");
                if (split != null && split.length == 2) {
                    allConditionBoolBuilder.filter(
                            f -> f.nested(
                                    n -> n.path("attributeValueIndexList")
                                            .query(q -> q.bool(
                                                    b -> b.must(m -> m.term(t -> t.field("attributeValueIndexList.attributeId").value(split[0])))
                                                            .must(m -> m.term(t -> t.field("attributeValueIndexList.valueId").value(split[1])))
                                            ))
                            )
                    );
                }
            }
        }
        builder.query(allConditionBoolBuilder.build()._toQuery());
        //3.设置分页参数，请求头参数"from","size"
        Integer pageSize = albumIndexQuery.getPageSize();
        Integer pageNo = albumIndexQuery.getPageNo();
        int from = (pageNo - 1) * pageSize;
        builder.from(from).size(pageSize);

        //4.设置高亮，请求体参数中"highlight"
        if (StringUtils.isNotBlank(keyword)) {
            builder.highlight(h -> h.fields("albumTitle", f -> f.preTags("<font style='color:red'>").postTags("</font>")));
        }

        //5.设置动态排序，请求体参数"sort"
        //5.1 前端提交排序参数值 形式=排序字段:排序方式   (综合)1:desc (播放量)2:desc
        String order = albumIndexQuery.getOrder();
        if (StringUtils.isNotBlank(order)) {
            String[] split = order.split(":");
            if (split != null && split.length == 2) {
                String orderFiled = "";
                switch (split[0]) {
                    case "1":
                        orderFiled = "hotScore";
                        break;
                    case "2":
                        orderFiled = "playStatNum";
                        break;
                    case "3":
                        orderFiled = "createTime";
                        break;
                }
                SortOrder sortOrder = "asc".equals(split[1]) ? SortOrder.Asc : SortOrder.Desc;
                String finalOrderFiled = orderFiled;
                builder.sort(s -> s.field(f -> f.field(finalOrderFiled).order(sortOrder)));
            }
        }

        //6.设置精简业务字段，请求体参数"_source"
        builder.source(s -> s.filter(f -> f.includes("id", "payType", "albumTitle", "albumIntro", "coverUrl", "includeTrackCount", "playStatNum", "announcerName")));

        //7.返回检索请求对象
        return builder.build();
    }

    /**
     * 解析ES检索响应结果，封装为自定义VO对象
     *
     * @param searchResponse  ES检索响应结果
     * @param albumIndexQuery 查询条件需要获取分页信息
     * @return
     */
    @Override
    public AlbumSearchResponseVo parseSearchResult(SearchResponse<AlbumInfoIndex> searchResponse, AlbumIndexQuery albumIndexQuery) {
        //1.构建结果VO对象
        AlbumSearchResponseVo vo = new AlbumSearchResponseVo();
        //2.封装VO对象中分页相关属性
        //2.1 从入参条件对象里获取页码 页大小
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        //2.2 从ES响应结果中解析总记录数
        HitsMetadata<AlbumInfoIndex> hits = searchResponse.hits();
        long total = hits.total().value();
        //2.3 计算得出总页数
        long totalPages = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
        vo.setPageNo(pageNo);
        vo.setPageSize(pageSize);
        vo.setTotal(total);
        vo.setTotalPages(totalPages);
        //3.封装检索到业务数据集合
        List<Hit<AlbumInfoIndex>> hitsList = hits.hits();
        if (CollectionUtil.isNotEmpty(hitsList)) {
            //3.1 将集合泛型从Hit类型转为AlbumInfoIndexVo
            List<AlbumInfoIndexVo> list = hitsList.stream()
                    .map(hit -> {
                        AlbumInfoIndex source = hit.source();
                        AlbumInfoIndexVo albumInfoIndexVo = BeanUtil.copyProperties(source, AlbumInfoIndexVo.class);
                        //3.2 获取高亮片段
                        Map<String, List<String>> highlightMap = hit.highlight();
                        if (CollectionUtil.isNotEmpty(highlightMap)) {
                            if (highlightMap.containsKey("albumTitle")) {
                                String hightLightTitile = highlightMap.get("albumTitle").get(0);
                                albumInfoIndexVo.setAlbumTitle(hightLightTitile);
                            }
                        }
                        return albumInfoIndexVo;
                    }).collect(Collectors.toList());
            vo.setList(list);
        }
        //4.响应结果VO
        return vo;
    }

    /**
     * 查询1级分类下置顶3级分类热度top6
     *
     * @param category1Id
     * @return [{"baseCategory3":{三级分类对象},"list":[top6集合数据]},{其他置顶分类热门对象}]
     */
    @Override
    public List<Map<String, Object>> getCategory3Top6(Long category1Id) {
        try {
            //1.远程专辑服务获取置顶三级分类集合
            //1.1 获取置顶三级分类集合，得到三级分类ID列表
            List<BaseCategory3> baseCategory3List = albumFeignClient.getTopBaseCategory3(category1Id).getData();
            Assert.notNull(baseCategory3List, "分类：{}列表不存在", category1Id);

            List<Long> category3IdList = baseCategory3List.stream().map(BaseCategory3::getId).collect(Collectors.toList());

            //1.2 将三级分类集合泛型从Long转为FieldValue类型
            List<FieldValue> fieldValueList = category3IdList.stream().map(category3Id -> FieldValue.of(category3Id)).collect(Collectors.toList());

            //1.3 将分类集合转为Map 其中key=三级分类ID  Value=三级分类对象 后续封装分类信息
            Map<Long, BaseCategory3> category3Map = baseCategory3List.stream().collect(Collectors.toMap(a -> a.getId(), a -> a));

            //2.执行检索：构建完整检索请求对象
            String c3IdAggName = "category3_agg";
            String top6AggName = "top6";
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
                    s ->
                            s.index(INDEX_NAME)  //指定查询索引库
                                    .size(0)  //页大小设置为0
                                    .query(q -> q.terms(t -> t.field("category3Id").terms(t1 -> t1.value(fieldValueList))))
                                    .aggregations(
                                            c3IdAggName,
                                            a -> a.terms(t -> t.field("category3Id").size(7))
                                                    .aggregations(
                                                            top6AggName,
                                                            t -> t.topHits(t1 -> t1.size(6).sort(s1 -> s1.field(f -> f.field("hotScore").order(SortOrder.Desc))))
                                                    )
                                    )
                    ,
                    AlbumInfoIndex.class
            );

            //3.解析ES检索结果
            //3.1 获取三级分类ID聚合结果对象
            Aggregate category3Aggregate = searchResponse.aggregations().get(c3IdAggName);
            //3.1.1 由于3级分类ID是Long类型 结果对象LongTermsAggregate
            LongTermsAggregate category3lterms = category3Aggregate.lterms();
            List<LongTermsBucket> termsBucketList = category3lterms.buckets().array();
            //3.2 遍历三级分类ID聚合结果桶集合 每遍历一次处理一个桶-对应一个三级分类热门对象
            List<Map<String, Object>> list = termsBucketList.stream()
                    .map(bucket -> {
                        //3.2.1 获取三级分类ID
                        long topCatgory3Id = bucket.key();
                        BaseCategory3 baseCategory3 = category3Map.get(topCatgory3Id);
                        //3.2.2 继续下钻获取子聚合
                        TopHitsAggregate topHitsAggregate = bucket.aggregations().get(top6AggName).topHits();
                        //3.3 将子聚合集合泛型从Hit类型AlbumInfoIndex类型-->得到置顶6个热门专辑
                        List<AlbumInfoIndex> top6List = topHitsAggregate.hits().hits().stream().map(hit -> {
                            String albumInfoIndexJsonStr = hit.source().toString();
                            AlbumInfoIndex albumInfoIndex = JSONObject.parseObject(albumInfoIndexJsonStr, AlbumInfoIndex.class);
                            return albumInfoIndex;
                        }).collect(Collectors.toList());
                        //3.4 封装分类热门对象
                        Map<String, Object> map = new HashMap<>();
                        map.put("baseCategory3", baseCategory3);
                        map.put("list", top6List);
                        return map;
                    }).collect(Collectors.toList());
            return list;
        } catch (Exception e) {
            log.error("[搜索服务]首页置顶分类热门专辑异常：{}", e);
            throw new RuntimeException(e);
        }
    }

    @Autowired
    private SuggestIndexRepository suggestIndexRepository;

    /**
     * 将专辑标题存入提词索引库
     *
     * @param albumInfoIndex
     */
    @Override
    public void saveSuggestIndex(AlbumInfoIndex albumInfoIndex) {
        //1.构建索引库文档对象
        SuggestIndex suggestIndex = new SuggestIndex();
        suggestIndex.setId(albumInfoIndex.getId().toString());
        String albumTitle = albumInfoIndex.getAlbumTitle();
        suggestIndex.setTitle(albumTitle);
        suggestIndex.setKeyword(new Completion(new String[]{albumTitle}));
        //1.1 将汉字转为汉语拼音 jing dian liu sheng ji
        String albumTitlePinyin = PinyinUtil.getPinyin(albumTitle, "");
        suggestIndex.setKeywordPinyin(new Completion(new String[]{albumTitlePinyin}));
        //1.1 将汉字转为汉语拼音首字母
        String albumTitleFirstLetter = PinyinUtil.getFirstLetter(albumTitle, "");
        suggestIndex.setKeywordSequence(new Completion(new String[]{albumTitleFirstLetter}));

        //2.存入提词文档记录到提词索引库
        suggestIndexRepository.save(suggestIndex);

    }


    private static final String SUGGEST_INDEX = "suggestinfo";

    /**
     * 根据用户已录入字符查询提词索引库，自动补全提示词
     *
     * @param keyword
     * @return
     */
    @Override
    public List<String> completeSuggest(String keyword) {
        try {
            String suggestKeyword = "suggestKeyword";
            String suggestPinyin = "suggestPinyin";
            String suggestSequence = "suggestSequence";
            //1.执行自动补全检索请求
            //1.1 尝试进行关键字自动补全，分别检索三个建议词字段：汉字、拼音、首字母
            SearchResponse<SuggestIndex> searchResponse = elasticsearchClient.search(
                    s -> s.index(SUGGEST_INDEX)
                            .suggest(
                                    s1 -> s1.suggesters(suggestKeyword, fs -> fs.prefix(keyword).completion(c -> c.size(10).field("keyword").skipDuplicates(true)))
                                            .suggesters(suggestPinyin, fs -> fs.prefix(keyword).completion(c -> c.size(10).field("keywordPinyin").skipDuplicates(true).fuzzy(f -> f.fuzziness("1"))))
                                            .suggesters(suggestSequence, fs -> fs.prefix(keyword).completion(c -> c.size(10).field("keywordSequence").skipDuplicates(true)))
                            ),
                    SuggestIndex.class
            );
            //2.解析自动补全结果
            //2.1 解析获取建议词结果:从建议词结果中三个自定义建议名称中获取
            Set<String> suggestSet = new HashSet<>();
            suggestSet.addAll(this.parseSuggestResult(suggestKeyword, searchResponse));
            suggestSet.addAll(this.parseSuggestResult(suggestPinyin, searchResponse));
            suggestSet.addAll(this.parseSuggestResult(suggestSequence, searchResponse));

            //2.2 如果解析建议词结果数量小于10，采用全文检索专辑索引库标题补全到10个
            if (suggestSet.size() < 10) {
                //2.2.1 执行全文查询
                SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(
                        s -> s.index(INDEX_NAME).query(q -> q.match(m -> m.field("albumTitle").query(keyword))),
                        AlbumInfoIndex.class);
                //2.2.2 解析全文检索结果
                List<Hit<AlbumInfoIndex>> hits = response.hits().hits();
                if (CollectionUtil.isNotEmpty(hits)) {
                    for (Hit<AlbumInfoIndex> hit : hits) {
                        AlbumInfoIndex albumInfoIndex = hit.source();
                        suggestSet.add(albumInfoIndex.getAlbumTitle());
                        if (suggestSet.size() >= 10) {
                            break;
                        }
                    }
                }
            }
            //2.3 返回结果时候最多返回10个，去重处理
            List<String> suggestList = new ArrayList<>(suggestSet);
            if (suggestList.size() >= 10) {
                return suggestList.subList(0, 10);
            }
            return suggestList;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 解析建议词结果
     *
     * @param suggestName
     * @param searchResponse
     * @return
     */
    @Override
    public Collection<String> parseSuggestResult(String suggestName, SearchResponse<SuggestIndex> searchResponse) {
        //1.声明提示词结果集合
        List<String> list = new ArrayList<>();
        Map<String, List<Suggestion<SuggestIndex>>> suggestMap = searchResponse.suggest();
        //2.从建议词响应结果中根据自定义建议名称获取建议结果
        List<Suggestion<SuggestIndex>> suggestions = suggestMap.get(suggestName);
        for (Suggestion<SuggestIndex> suggestion : suggestions) {
            CompletionSuggest<SuggestIndex> completion = suggestion.completion();
            List<CompletionSuggestOption<SuggestIndex>> options = completion.options();
            if (CollectionUtil.isNotEmpty(options)) {
                for (CompletionSuggestOption<SuggestIndex> option : options) {
                    //获取建议词结果中原始内容：专辑标题
                    SuggestIndex source = option.source();
                    String title = source.getTitle();
                    list.add(title);
                }
            }
        }
        return list;
    }

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 更新ES索引库文档统计信息
     *
     * @param trackStatMqVo
     */
    @Override
    public void updateAlbumStat(TrackStatMqVo trackStatMqVo) {
        //1.避免同一个统计消息被重复消息，利用Redis命令set k v [ex][nx]某次统计消息只有第一次能存入成功
        //1.1 创建key
        String key = RedisConstant.BUSINESS_PREFIX + "search:" + trackStatMqVo.getBusinessNo();
        //1.2 尝试存入redis
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "", 10, TimeUnit.MINUTES);
        if (flag) {
            Optional<AlbumInfoIndex> optional = albumInfoIndexRepository.findById(trackStatMqVo.getAlbumId());
            if (optional.isPresent()) {
                AlbumInfoIndex albumInfoIndex = optional.get();
                if (SystemConstant.TRACK_STAT_PLAY_NAME.equals(trackStatMqVo.getStatType())) {
                    albumInfoIndex.setPlayStatNum(albumInfoIndex.getPlayStatNum() + trackStatMqVo.getCount());
                }
                if (SystemConstant.TRACK_STAT_COMMENT_NAME.equals(trackStatMqVo.getStatType())) {
                    albumInfoIndex.setCommentStatNum(albumInfoIndex.getCommentStatNum() + trackStatMqVo.getCount());
                }
                albumInfoIndexRepository.save(albumInfoIndex);
            }
        }
    }

    /**
     * 更新Redis中所有一级分类下不同排序字段TOP10专辑列表
     *
     * @return
     */
    @Override
    public void updateLatelyAlbumRanking() {
        try {
            //1.远程调用“专辑服务”得到所有一级分类，获取所有一级分类ID
            List<BaseCategory1> list = albumFeignClient.getAllCategory1().getData();
            if (CollectionUtil.isNotEmpty(list)) {
                //2.遍历所有一级分类
                List<Long> categoryIdList = list.stream().map(BaseCategory1::getId).collect(Collectors.toList());
                for (Long category1Id : categoryIdList) {
                    //声明Rdis排行榜中Key-标识出分类
                    String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
                    //3.处理某个一级分类排行数据，遍历5种不同排序字段
                    String[] rankingDimensionArray = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};
                    for (String dimension : rankingDimensionArray) {
                        //4.根据分类ID+排序字段检索ES获取TOP10专辑列表
                        SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(s ->
                                        s.index(INDEX_NAME)
                                                .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
                                                .size(10)
                                                .sort(s1 -> s1.field(f -> f.field(dimension).order(SortOrder.Desc))),
                                AlbumInfoIndex.class
                        );
                        List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits();
                        if (CollectionUtil.isNotEmpty(hits)) {
                            //5.将当前分类，当前排序下top10存入Redis
                            List<AlbumInfoIndex> infoIndexList = hits.stream().map(hit -> hit.source()).collect(Collectors.toList());
                            //5.1 构建Redis中排行榜hash中field(hashKey)-标识出排序字段
                            String field = dimension;
                            //5.2 将当前分类下，当前排序字段 top10列表存入Redis
                            redisTemplate.opsForHash().put(key, field, infoIndexList);
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("[搜索服务]更新Redis排行榜异常：{}", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 查询指定分类制定排行维度TOP10专辑
     *
     * @param category1Id
     * @param dimension
     * @return
     */
    @Override
    public List<AlbumInfoIndex> getRankingList(Long category1Id, String dimension) {
        //声明Rdis排行榜中Key-标识出分类 方式一
        String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
        //Boolean flag = redisTemplate.opsForHash().hasKey(key, dimension);
        //if (flag) {
        //    List<AlbumInfoIndex> list = (List<AlbumInfoIndex>) redisTemplate.opsForHash().get(key, dimension);
        //    return list;
        //}
        //方式二：通过获取绑定到指定Key的操作对象（决定操作哪个大Key）
        BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(key);
        if (hashOps.hasKey(dimension)) {
            List<AlbumInfoIndex> list = hashOps.get(dimension);
            return list;
        }
        return null;
    }
}
